////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2016-2018 Qualcomm Technologies, Inc.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file camxmetadatapool.cpp
/// @brief Implements camx metadata slot and pool
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "camxatomic.h"
#include "camxhal3metadatautil.h"
#include "camxmem.h"
#include "camxmetadatapool.h"
#include "camxtrace.h"
#include "camxvendortags.h"

CAMX_NAMESPACE_BEGIN

// Allow the list, holding property pool clients, to grow
static const UINT32 PropertyPoolClientGrowSize  = 4;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::MetadataPoolThreadCb
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID* MetadataPool::MetadataPoolThreadCb(
    VOID* pArg)
{
    CamxResult result = CamxResultSuccess;

    if (NULL != pArg)
    {
        MetadataPool* pLocalInstance = static_cast<MetadataPool*>(pArg);

        result = pLocalInstance->Initialize();
    }
    else
    {
        CAMX_LOG_ERROR(CamxLogGroupCore, "NULL argument in MetadataPoolThreadCb");
    }
    return NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Create
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MetadataPool* MetadataPool::Create(
    PoolType        poolType,
    UINT            pipelineId,
    ThreadManager*  pThreadManager,
    UINT            numSlots)
{
    CAMX_ENTRYEXIT_SCOPE(CamxLogGroupCore, SCOPEEventMetadataPoolCreate);

    CamxResult      result          = CamxResultSuccess;
    MetadataPool*   pLocalInstance  = NULL;

    pLocalInstance = CAMX_NEW MetadataPool(poolType, pipelineId, pThreadManager, numSlots);
    if (NULL != pLocalInstance)
    {
        if (NULL != pThreadManager)
        {
            result = pLocalInstance->m_pThreadManager->PostJob(pLocalInstance->m_hThread,
                                                               NULL,
                                                               reinterpret_cast<VOID**>(&pLocalInstance),
                                                               FALSE,
                                                               FALSE);
            if (CamxResultSuccess != result)
            {
                CAMX_LOG_ERROR(CamxLogGroupCore, "[%s] PostJob failed.", pLocalInstance->GetPoolIdentifier());
            }
        }
        else
        {
            // pThreadManager is NULL, we are not parallelizing this pool creation.
            result = pLocalInstance->Initialize();
        }

        if (CamxResultSuccess != result)
        {
            CAMX_DELETE pLocalInstance;
            pLocalInstance = NULL;
        }
    }

    return pLocalInstance;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Destroy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::Destroy()
{
    CAMX_ENTRYEXIT_SCOPE(CamxLogGroupCore, SCOPEEventMetadataPoolDestroy);

    CAMX_DELETE this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::MetadataPool
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MetadataPool::MetadataPool(
    PoolType        poolType,
    UINT            pipelineId,
    ThreadManager*  pThreadManager,
    UINT            numSlots)
    : m_poolType(poolType)
    , m_numSlots(numSlots)
    , m_pipelineId(pipelineId)
{
    CamxResult result = CamxResultSuccess;

    switch (m_poolType)
    {
        case PoolType::Static:
        case PoolType::PerUsecase:
            // Static and Usecase pools must have only 1 slot
            CAMX_ASSERT(1 == m_numSlots);
            break;

        case PoolType::PerFrameInput:
        case PoolType::PerFrameResult:
        case PoolType::PerFrameResultEarly:
        case PoolType::PerFrameInternal:
        case PoolType::PerFrameDebugData:
        case PoolType::PerFrameEmptyMetadata:
            // Assuming the number of slots can be evenly divided by 2, else the input pool sparse pointer logic will fail
            CAMX_ASSERT(0 == (m_numSlots % 2));
            break;

        default:
            break;
    }

    // We do the following as bare minimum initialization,
    // offload the remaining to a different thread.

    CAMX_ASSERT_MESSAGE(CamxResultSuccess == result, "Subscription list allocs failed");

    OsUtils::SNPrintF(m_poolIdentifier,
                      MaxTraceStringLength,
                      "Metadata Type: %s P:%p",
                      PoolTypeStrings[static_cast<UINT>(m_poolType)],
                      this);

    m_pThreadManager            = pThreadManager;
    m_pMetadataPoolCreateWait   = Condition::Create("MetadataPoolCreateWait");
    m_pMetadataPoolCreateLock   = Mutex::Create("MetadataPoolCreateLock");
    m_poolStatus                = PoolStatus::Uninitialized;

    if (NULL != m_pThreadManager)
    {
        result = m_pThreadManager->RegisterJobFamily(MetadataPoolThreadCb,
                                                     GetPoolIdentifier(),
                                                     NULL,
                                                     JobPriority::Normal,
                                                     TRUE,
                                                     &m_hThread);
        if (CamxResultSuccess != result)
        {
            CAMX_LOG_ERROR(CamxLogGroupCore, "[%s] Register MetadaPoolThreadManager failed!",
                           GetPoolIdentifier());
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::~MetadataPool
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MetadataPool::~MetadataPool()
{
    UINT32 i = 0;

    // Unregister jobs first, we can not handle any call during destroy
    if ((InvalidJobHandle != m_hThread) && (NULL != m_pThreadManager))
    {
        m_pThreadManager->UnregisterJobFamily(MetadataPoolThreadCb,
                                              GetPoolIdentifier(),
                                              m_hThread);
    }

    for (i = 0; i < m_numSlots; i++)
    {
        if (NULL != m_pSlots[i])
        {
            m_pSlots[i]->Destroy();
            m_pSlots[i] = NULL;
        }
    }

    for (i = 0; i < MaxMetadataTags; i++)
    {
        if (NULL != m_pMetadataClients[i])
        {
            LDLLNode* pNode = m_pMetadataClients[i]->RemoveFromHead();

            while (NULL != pNode)
            {
                CAMX_FREE(pNode->pData);
                CAMX_FREE(pNode);
                pNode = m_pMetadataClients[i]->RemoveFromHead();
            }

            CAMX_DELETE m_pMetadataClients[i];
            m_pMetadataClients[i] = NULL;
        }
    }

    for (i = 0; i < MaxProperties; i++)
    {
        if (NULL != m_pPropertyClients[i])
        {
            LDLLNode* pNode = m_pPropertyClients[i]->RemoveFromHead();

            while (NULL != pNode)
            {
                CAMX_FREE(pNode->pData);
                CAMX_FREE(pNode);
                pNode = m_pPropertyClients[i]->RemoveFromHead();
            }

            CAMX_DELETE m_pPropertyClients[i];
            m_pPropertyClients[i] = NULL;
        }
    }

    if (NULL != m_pStickyData)
    {
        CAMX_FREE(m_pStickyData);
        m_pStickyData = NULL;
    }

    if (NULL != m_pDebugDataBuffer)
    {
        CAMX_FREE(m_pDebugDataBuffer);
        m_pDebugDataBuffer = NULL;
    }

    if (NULL != m_pPoolLock)
    {
        m_pPoolLock->Destroy();
        m_pPoolLock = NULL;
    }

    if (NULL != m_pMetadataPoolCreateLock)
    {
        m_pMetadataPoolCreateLock->Destroy();
        m_pMetadataPoolCreateLock = NULL;
    }

    if (NULL != m_pMetadataPoolCreateWait)
    {
        m_pMetadataPoolCreateWait->Destroy();
        m_pMetadataPoolCreateWait = NULL;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Initialize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataPool::Initialize()
{
    CAMX_ENTRYEXIT_NAME(CamxLogGroupCore, "MetadataPoolInit");
    CamxResult  result      = CamxResultSuccess;
    UINT32      i           = 0;

    CalculateMetadataSize();

    for (i = 0; i < m_numSlots; i++)
    {
        m_pSlots[i] = MetadataSlot::Create(m_slotMetadataEntryCount, m_slotMetadataDataSize, this);

        CAMX_ASSERT_MESSAGE(NULL != m_pSlots[i], "Failed to create slot %d. Many things will break", i);

        if (NULL == m_pSlots[i])
        {
            result = CamxResultEFailed;
            break;
        }
    }

    if ((CamxResultSuccess == result) && (PoolType::PerFrameInput == m_poolType))
    {
        m_pStickyData = CAMX_CALLOC_ALIGNED(m_numSlots * HAL3MetadataUtil::GetMaxMetadataSize(), alignof(MAXALIGN_T));

        CAMX_ASSERT(NULL != m_pStickyData);

        if (NULL == m_pStickyData)
        {
            result = CamxResultENoMemory;
        }
        else
        {
            SIZE_T sizePerSlot      = HAL3MetadataUtil::GetMaxMetadataSize();
            SIZE_T middleBaseOffset = (m_numSlots / 2) * sizePerSlot;

            // Initialize all the pointers for the bounds cases which will always be updated to be the latest
            for (i = 0; i < HAL3MetadataUtil::GetAllMetadataTagCount(); i++)
            {
                SIZE_T offset = HAL3MetadataUtil::GetOffsetByIndex(i);

                CAMX_ASSERT_MESSAGE(
                    ((0 == i) || (HAL3MetadataUtil::GetOffsetByIndex(i - 1) <= offset)),
                    "Index %d: Metadata offset %d not greater than previous: %d",
                    i, offset, HAL3MetadataUtil::GetOffsetByIndex(i - 1));

                m_pSlots[0]->UpdateStickyDataPtr(i, Utils::VoidPtrInc(m_pStickyData, offset));
                m_pSlots[m_numSlots / 2]->UpdateStickyDataPtr(i, Utils::VoidPtrInc(m_pStickyData, (middleBaseOffset + offset)));
            }
        }
    }

    if ((CamxResultSuccess              == result)     &&
        (PoolType::PerFrameDebugData    == m_poolType) &&
        (0 < HAL3MetadataUtil::DebugDataSize(DebugDataType::MaxTypes)))
    {
        result = InitializeDebugDataPool();
    }

    if (CamxResultSuccess == result)
    {
        m_pPoolLock = Mutex::Create(m_poolIdentifier);
        if (NULL == m_pPoolLock)
        {
            CAMX_ASSERT(NULL != m_pPoolLock);
            result = CamxResultENoMemory;
        }
    }

    m_pMetadataPoolCreateLock->Lock();

    if (CamxResultSuccess == result)
    {
        m_poolStatus = PoolStatus::Initialized;
        CAMX_LOG_INFO(CamxLogGroupCore, "[%s] Metadata pool initialized.", GetPoolIdentifier());
    }
    else
    {
        m_poolStatus = PoolStatus::Error;
        CAMX_LOG_ERROR(CamxLogGroupCore, "[%s] Metadata pool initialize failed.", GetPoolIdentifier());
    }

    m_pMetadataPoolCreateWait->Signal();

    m_pMetadataPoolCreateLock->Unlock();

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Subscribe
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataPool::Subscribe(
    const Subscription*     pSubscribe,
    UINT32                  count,
    IPropertyPoolObserver*  pClient,
    const CHAR*             pClientName)
{
    CAMX_ASSERT((PoolType::PerFrameInput != m_poolType) && (PoolType::PerFrameDebugData != m_poolType));

    CamxResult  result  = CamxResultSuccess;
    UINT32      i       = 0;
    SIZE_T      index   = InvalidIndex;

    if ((count > (MaxProperties + MaxMetadataTags)) ||
        (NULL == pSubscribe)                        ||
        (NULL == pClient))
    {
        return CamxResultEFailed;
    }

    for (i = 0; i < count; i++)
    {
        LDLLNode*          pNode  = static_cast<LDLLNode*>(CAMX_CALLOC(sizeof(LDLLNode)));
        SubscriptionEntry* pEntry = static_cast<SubscriptionEntry*>(CAMX_CALLOC(sizeof(SubscriptionEntry)));

        if ((NULL != pEntry) && (NULL != pNode))
        {
            pEntry->pClient     = pClient;
            pEntry->pClientName = pClientName;

            pNode->pData = pEntry;

            switch (pSubscribe[i].type)
            {
                case UnitType::Property:
                    index = FindIndexByPropertyId(pSubscribe[i].unitId);
                    if ((InvalidIndex != index) && (index < MaxProperties))
                    {
                        // Defer m_pPropertyClients list creation to this point
                        if (NULL == m_pPropertyClients[index])
                        {
                            m_pPropertyClients[index] = CAMX_NEW LightweightDoublyLinkedList();
                        }

                        if (NULL == m_pPropertyClients[index])
                        {
                            CAMX_FREE(pEntry);
                            CAMX_FREE(pNode);
                            pEntry = NULL;
                            pNode  = NULL;
                            result = CamxResultENoMemory;
                        }
                        else
                        {
                            m_pPropertyClients[index]->InsertToTail(pNode);
                        }
                    }
                    else
                    {
                        // We return error, but will try to proceed for the other entries
                        result =  CamxResultENoSuch;
                    }

                    break;

                case UnitType::Metadata:
                    index = CAMX_TAG_TO_INDEX(pSubscribe[i].unitId);
                    if ((InvalidIndex != index) && (index < MaxMetadataTags))
                    {
                        // Defer m_pMetadataClients list creation to this point
                        if (NULL == m_pMetadataClients[index])
                        {
                            m_pMetadataClients[index] = CAMX_NEW LightweightDoublyLinkedList();
                        }

                        if (NULL == m_pMetadataClients[index])
                        {
                            CAMX_FREE(pEntry);
                            CAMX_FREE(pNode);
                            pEntry = NULL;
                            pNode  = NULL;
                            result = CamxResultENoMemory;
                        }
                        else
                        {
                            m_pMetadataClients[index]->InsertToTail(pNode);
                        }
                    }
                    else
                    {
                        // We return error, but will try to proceed for the other entries
                        result =  CamxResultENoSuch;
                    }

                    break;

                default:
                    CAMX_ASSERT_ALWAYS_MESSAGE("Unknown subscription type: %d", pSubscribe[i].type);
                    break;
            }
        }
        else
        {
            result = CamxResultENoMemory;
            if (NULL != pNode)
            {
                CAMX_FREE(pNode);
                pNode = NULL;
            }
            if (NULL != pEntry)
            {
                CAMX_FREE(pEntry);
                pEntry = NULL;
            }
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::SubscribeAll
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataPool::SubscribeAll(
    IPropertyPoolObserver*  pClient,
    const CHAR*             pClientName)
{
    CAMX_ASSERT((PoolType::PerFrameInput != m_poolType) && (PoolType::PerFrameDebugData != m_poolType));

    UINT client;

    for (client = 0; client < MaxSubscribers; client ++)
    {
        if (NULL == m_subscribeAllClients[client].pClient)
        {
            m_subscribeAllClients[client] = { pClient, pClientName };
            break;
        }
    }

    return (MaxSubscribers > client) ? CamxResultSuccess : CamxResultENoMemory;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::UnsubscribeAll
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::UnsubscribeAll(
    IPropertyPoolObserver* pClient)
{
    CAMX_ASSERT((PoolType::PerFrameInput != m_poolType) && (PoolType::PerFrameDebugData != m_poolType));

    for (UINT client = 0; client < MaxSubscribers; client ++)
    {
        if (m_subscribeAllClients[client].pClient == pClient)
        {
            m_subscribeAllClients[client] = { NULL, NULL };
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::NotifyImmediate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::NotifyImmediate(
    UnitType    type,
    UINT32      unitId,
    SIZE_T      index,
    UINT64      slotRequestId,
    BOOL        isSuccess)
{
    CAMX_ENTRYEXIT(CamxLogGroupStats);
    CAMX_ASSERT((PoolType::PerFrameInput != m_poolType) && (PoolType::PerFrameDebugData != m_poolType));

    LightweightDoublyLinkedList* pList      = NULL;
    UINT                         pipelineId = GetPipelineId();

    switch (type)
    {
        case UnitType::Property:
            pList = m_pPropertyClients[index];
            if (NULL != pList)
            {
                LDLLNode* pNode = pList->Head();

                while (NULL != pNode)
                {
                    SubscriptionEntry* pEntry = static_cast<SubscriptionEntry*>(pNode->pData);

                    CAMX_TRACE_MESSAGE_F(CamxLogGroupCore, "Notifying %s of %s for requestId %llu, Property %08x",
                        pEntry->pClientName,
                        (isSuccess == TRUE ? "Success" : "Failure"), slotRequestId, unitId);

                    if (TRUE == isSuccess)
                    {
                        pEntry->pClient->OnPropertyUpdate(unitId, slotRequestId, pipelineId);
                    }
                    else
                    {
                        pEntry->pClient->OnPropertyFailure(unitId, slotRequestId, pipelineId);
                    }
                    pNode = LightweightDoublyLinkedList::NextNode(pNode);
                }
            }

            for (UINT client = 0; client < MaxSubscribers; client++)
            {
                SubscriptionEntry* pEntry = &m_subscribeAllClients[client];

                if (NULL != pEntry->pClient)
                {
                    CAMX_TRACE_MESSAGE_F(CamxLogGroupCore, "Notifying %s of %s for requestId %llu, Property %08x",
                        pEntry->pClientName,
                        (isSuccess == TRUE ? "Success" : "Failure"), slotRequestId, unitId);

                    if (TRUE == isSuccess)
                    {
                        pEntry->pClient->OnPropertyUpdate(unitId, slotRequestId, pipelineId);
                    }
                    else
                    {
                        pEntry->pClient->OnPropertyFailure(unitId, slotRequestId, pipelineId);
                    }
                }
            }

            break;

        case UnitType::Metadata:
            pList = m_pMetadataClients[index];
            if (NULL != pList)
            {
                LDLLNode* pNode = pList->Head();

                while (NULL != pNode)
                {
                    SubscriptionEntry* pEntry = static_cast<SubscriptionEntry*>(pNode->pData);

                    CAMX_TRACE_MESSAGE_F(CamxLogGroupCore, "Notifying %s of %s for requestId %llu, Metadata %08x",
                        pEntry->pClientName,
                        (isSuccess == TRUE ? "Success" : "Failure"), slotRequestId, unitId);

                    if (TRUE == isSuccess)
                    {
                        pEntry->pClient->OnMetadataUpdate(unitId, slotRequestId, pipelineId);
                    }
                    else
                    {
                        pEntry->pClient->OnMetadataFailure(unitId, slotRequestId, pipelineId);
                    }
                    pNode = LightweightDoublyLinkedList::NextNode(pNode);
                }
            }

            for (UINT client = 0; client < MaxSubscribers; client++)
            {
                SubscriptionEntry* pEntry = &m_subscribeAllClients[client];

                if (NULL != pEntry->pClient)
                {
                    CAMX_TRACE_MESSAGE_F(CamxLogGroupCore, "Notifying %s of %s for requestId %llu, Metadata %08x",
                        pEntry->pClientName,
                        (isSuccess == TRUE ? "Success" : "Failure"), slotRequestId, unitId);

                    if (TRUE == isSuccess)
                    {
                        pEntry->pClient->OnMetadataUpdate(unitId, slotRequestId, pipelineId);
                    }
                    else
                    {
                        pEntry->pClient->OnMetadataFailure(unitId, slotRequestId, pipelineId);
                    }
                }
            }

            break;

        default:
            CAMX_ASSERT_ALWAYS_MESSAGE("Unknown unittype for notify immediate: %d", static_cast<UINT>(type));
            break;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Flush
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::Flush()
{
    for (UINT32 i = 0; i < m_numSlots; i++)
    {
        m_pSlots[i]->Invalidate();
        m_pSlots[i]->ResetMetadata();
    }

    /// @todo (CAMX-2010) Finalize metadata pool reset/invalidate coverage; we are not cleaning up subscribers and all yet
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Invalidate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::Invalidate(
    UINT64 requestId)
{
    // We only slide the window once all of the clients waiting on the current frame have been notified

    m_pPoolLock->Lock();

    MetadataSlot* pCurrentSlot = GetSlot(requestId);

    if (NULL != pCurrentSlot)
    {
        pCurrentSlot->Invalidate();
        pCurrentSlot->ResetMetadata();
    }

    ResetCurrentEarlyTagsCountForSlot(requestId);

    m_pPoolLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::UpdateRequestId
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::UpdateRequestId(
    UINT64 requestId)
{
    // We only update the next request ID when a process capture request is called into the HAL

    m_pPoolLock->Lock();

    MetadataSlot* pSlot = GetSlot(requestId);
    if (NULL != pSlot)
    {
        pSlot->SetSlotRequestId(requestId);
    }

    m_pPoolLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::CalculateMetadataSize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::CalculateMetadataSize()
{
    switch (m_poolType)
    {
        case PoolType::Static:
            HAL3MetadataUtil::CalculateSizeStaticMeta(&m_slotMetadataEntryCount, &m_slotMetadataDataSize);
            break;

        case PoolType::PerFrameInput:
            /// Data size is fixed. Need only know where in the fixed table to fetch each tag
            HAL3MetadataUtil::CalculateLinearOffsets();
            m_inputPoolPropertyOffset = CAMX_TAG_TO_INDEX(VendorTagPrivate);
            break;

        case PoolType::PerFrameResultEarly:
        case PoolType::PerFrameResult:
        case PoolType::PerUsecase:
            HAL3MetadataUtil::CalculateSizeAllMeta(&m_slotMetadataEntryCount, &m_slotMetadataDataSize);
            break;

        case PoolType::PerFrameInternal:
            m_slotMetadataEntryCount = 1;
            m_slotMetadataDataSize   = sizeof(InternalPropertyBlob);
            break;

        case PoolType::PerFrameDebugData:
            m_slotMetadataDataSize = 1;
            m_slotMetadataDataSize = sizeof(DebugDataPropertyBlob);
            break;

        case PoolType::PerFrameEmptyMetadata:
            m_slotMetadataEntryCount = 2;
            m_slotMetadataDataSize   = HAL3MetadataUtil::GetMaxSizeByTag(SensorTimestamp) * m_slotMetadataEntryCount;
            break;

        default:
            CAMX_ASSERT_ALWAYS_MESSAGE("Unknown pooltype for metadata size calc: %d", static_cast<UINT>(m_poolType));
            break;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::FindIndexByPropertyId
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UINT32 MetadataPool::FindIndexByPropertyId(
    PropertyID id)
{
    UINT              index           = InvalidIndex;
    static const UINT ResultIDsSize   = (PropertyIDPerFrameResultEnd - PropertyIDPerFrameResultBegin) + 1;
    static const UINT InternalIDsSize = (PropertyIDPerFrameInternalEnd - PropertyIDPerFrameInternalBegin) + 1;

    // Arbitrarily setup Result < Internal < Usecase
    if (PropertyIDPerFrameResultBegin == (id & DriverInternalGroupMask))
    {
        index = id & ~DriverInternalGroupMask;
    }
    else if (PropertyIDPerFrameInternalBegin == (id & DriverInternalGroupMask))
    {
        index = (id & ~DriverInternalGroupMask) + ResultIDsSize;
    }
    else if (PropertyIDUsecaseBegin == (id & DriverInternalGroupMask))
    {
        index = (id & ~DriverInternalGroupMask) + ResultIDsSize + InternalIDsSize;
    }

    return index;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// MetadataPool::UpdateSparse
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::UpdateSparse(
    UINT64    requestId,
    Metadata* pRequestMetadata)
{
    UINT32 slot     = (requestId % m_numSlots);
    UINT32 prevSlot = (0 == slot) ? m_numSlots - 1 : slot - 1;

    m_pSlots[slot]->CopyStickyCountsFromSlot(m_pSlots[prevSlot]);

    /// @note This is required because of the lifetime of references.
    ///       The code sets up m_numSlots == 2 * Request pipeline depth.
    ///       If the data is not copied on the bounds of the pipeline depth, request at m_numSlots - 1 can be
    ///       referencing slot 0 at the point of wrap around, when request m_numSlots arrives it can update slot 0,
    ///       overwriting the data required for active requests. By forcing copy to m_numSlots/2, requests will never
    ///       reference data from the other 'bank' of data.
    ///       The same issue arises at 0 and m_numSlots/2. If m_numSlots/2 - 1 references data from m_numSlots/2 the
    ///       next request will overwrite that data. By copying to slot 0 we ensure the data will always be valid until
    ///       the referencing requests complete
    if ((0 == slot) || ((m_numSlots / 2) == slot))
    {
        SIZE_T sizePerSlot = HAL3MetadataUtil::GetMaxMetadataSize();
        VOID*  pSlotData   = Utils::VoidPtrInc(m_pStickyData, ((slot == 0) ? 0 : (m_numSlots / 2) * sizePerSlot));

        for (UINT i = 0; i < HAL3MetadataUtil::GetAllMetadataTagCount(); i++)
        {
            // Copy data from *ptrs in prevSlot to slot
            VOID* pData;

            m_pSlots[prevSlot]->GetMetadataByIndex(i, &pData);

            if (NULL != pData)
            {
                Utils::Memcpy(Utils::VoidPtrInc(pSlotData, HAL3MetadataUtil::GetOffsetByIndex(i)),
                              pData,
                              HAL3MetadataUtil::GetMaxSizeByIndex(i));
            }
            else
            {
                CAMX_LOG_ERROR(CamxLogGroupCore, "Data pointer for tag at %d is null", i);
            }
            // Don't need to update pointers. The two slots that match are pre-updated to always point to their slot's data
        }
    }
    else
    {
        // Copy all pointers from prevSlot to slot
        m_pSlots[slot]->CopyStickyPtrsFromSlot(m_pSlots[prevSlot]);
    }

    CacheSparseMetadata(slot, pRequestMetadata);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// MetadataPool::CacheSparseMetadata
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::CacheSparseMetadata(
    UINT32    slot,
    Metadata* pRequestMetadata)
{
    if ((NULL != pRequestMetadata) && (slot < MaxPerFramePoolWindowSize))
    {
        SIZE_T entryCount = HAL3MetadataUtil::GetMetadataEntryCount(pRequestMetadata);
        VOID*  pSlotData  = Utils::VoidPtrInc(m_pStickyData, (slot * HAL3MetadataUtil::GetMaxMetadataSize()));

        for (UINT entryIndex = 0; entryIndex < entryCount; entryIndex++)
        {
            VOID*  pVal  = NULL;
            UINT32 tag   = MaxMetadataTags;
            SIZE_T count = 0;

            HAL3MetadataUtil::GetMetadataByIndex(pRequestMetadata, entryIndex, &tag, &count, &pVal);

            if (pVal != NULL)
            {
                // Assuming tag is valid if we get a pointer for the data back
                SIZE_T index = CAMX_TAG_TO_INDEX(tag);
                SIZE_T size  = HAL3MetadataUtil::GetMaxSizeByTag(tag);

                VOID* pData = Utils::VoidPtrInc(pSlotData, HAL3MetadataUtil::GetOffsetByIndex(index));

                // Update data
                Utils::Memcpy(pData, pVal, size);

                // Update slot pointer
                m_pSlots[slot]->UpdateStickyDataPtr(index, pData);
                // Update slot count
                m_pSlots[slot]->UpdateStickyDataCount(tag, index, count);
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::InitializeDebugDataPool
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataPool::InitializeDebugDataPool()
{
    CamxResult      result          = CamxResultSuccess;
    DebugData*      pDebugData      = {0};
    MetadataSlot*   pSlot           = NULL;
    VOID*           pBlob           = NULL;
    VOID*           pSlotDebugData  = NULL;

    m_pDebugDataBuffer      =
        CAMX_CALLOC_ALIGNED(m_numSlots * HAL3MetadataUtil::DebugDataSize(DebugDataType::MaxTypes), alignof(MAXALIGN_T));

    CAMX_ASSERT(NULL != m_pDebugDataBuffer);

    if (NULL == m_pDebugDataBuffer)
    {
        result = CamxResultENoMemory;
    }

    if (CamxResultSuccess == result)
    {
        for (UINT slotIndex = 0; slotIndex < m_numSlots; slotIndex++)
        {
            pSlot = m_pSlots[slotIndex];
            pSlot->GetPropertyBlob(&pBlob);

            // Set pointer to corresponding debug-data slot
            pSlotDebugData =
                Utils::VoidPtrInc(m_pDebugDataBuffer, (slotIndex * HAL3MetadataUtil::DebugDataSize(DebugDataType::MaxTypes)));

            pDebugData = reinterpret_cast<DebugData*>(
                         Utils::VoidPtrInc(pBlob, DebugDataPropertyOffsets[PropertyIDDebugDataAll & ~DriverInternalGroupMask]));
            pDebugData->size    = HAL3MetadataUtil::DebugDataSize(DebugDataType::MaxTypes);
            pDebugData->pData   = pSlotDebugData;

            pDebugData =
                reinterpret_cast<DebugData*>(
                         Utils::VoidPtrInc(pBlob, DebugDataPropertyOffsets[PropertyIDDebugDataAEC & ~DriverInternalGroupMask]));
            if (0 != HAL3MetadataUtil::DebugDataSize(DebugDataType::AEC))
            {
                pDebugData->size    = HAL3MetadataUtil::DebugDataSize(DebugDataType::AEC);
                pDebugData->pData   = Utils::VoidPtrInc(pSlotDebugData, HAL3MetadataUtil::DebugDataOffset(DebugDataType::AEC));
            }
            else
            {
                pDebugData->size    = 0;
                pDebugData->pData   = NULL;
            }

            pDebugData =
                reinterpret_cast<DebugData*>(
                      Utils::VoidPtrInc(pBlob, DebugDataPropertyOffsets[PropertyIDDebugDataAWB & ~DriverInternalGroupMask]));
            if (0 != HAL3MetadataUtil::DebugDataSize(DebugDataType::AWB))
            {
                pDebugData->size    = HAL3MetadataUtil::DebugDataSize(DebugDataType::AWB);
                pDebugData->pData   = Utils::VoidPtrInc(pSlotDebugData, HAL3MetadataUtil::DebugDataOffset(DebugDataType::AWB));
            }
            else
            {
                pDebugData->size    = 0;
                pDebugData->pData   = NULL;
            }

            pDebugData =
                reinterpret_cast<DebugData*>(
                         Utils::VoidPtrInc(pBlob, DebugDataPropertyOffsets[PropertyIDDebugDataAF  & ~DriverInternalGroupMask]));
            if (0 != HAL3MetadataUtil::DebugDataSize(DebugDataType::AF))
            {
                pDebugData->size    = HAL3MetadataUtil::DebugDataSize(DebugDataType::AF);
                pDebugData->pData   = Utils::VoidPtrInc(pSlotDebugData, HAL3MetadataUtil::DebugDataOffset(DebugDataType::AF));
            }
            else
            {
                pDebugData->size    = 0;
                pDebugData->pData   = NULL;
            }

            pDebugData =
                reinterpret_cast<DebugData*>(
                         Utils::VoidPtrInc(pBlob,
                                           DebugDataPropertyOffsets[PropertyIDTuningDataIFE  & ~DriverInternalGroupMask]));
            if (0 != HAL3MetadataUtil::DebugDataSize(DebugDataType::IFETuning))
            {
                pDebugData->size    = HAL3MetadataUtil::DebugDataSize(DebugDataType::IFETuning);
                pDebugData->pData   =
                    Utils::VoidPtrInc(pSlotDebugData, HAL3MetadataUtil::DebugDataOffset(DebugDataType::IFETuning));
            }
            else
            {
                pDebugData->size    = 0;
                pDebugData->pData   = NULL;
            }

            pDebugData =
                reinterpret_cast<DebugData*>(
                    Utils::VoidPtrInc(pBlob, DebugDataPropertyOffsets[PropertyIDTuningDataIPE  & ~DriverInternalGroupMask]));
            if (0 != HAL3MetadataUtil::DebugDataSize(DebugDataType::IPETuning))
            {
                pDebugData->size = HAL3MetadataUtil::DebugDataSize(DebugDataType::IPETuning);
                pDebugData->pData =
                    Utils::VoidPtrInc(pSlotDebugData, HAL3MetadataUtil::DebugDataOffset(DebugDataType::IPETuning));
            }
            else
            {
                pDebugData->size = 0;
                pDebugData->pData = NULL;
            }

            pDebugData =
                reinterpret_cast<DebugData*>(
                    Utils::VoidPtrInc(pBlob, DebugDataPropertyOffsets[PropertyIDTuningDataBPS  & ~DriverInternalGroupMask]));
            if (0 != HAL3MetadataUtil::DebugDataSize(DebugDataType::BPSTuning))
            {
                pDebugData->size = HAL3MetadataUtil::DebugDataSize(DebugDataType::BPSTuning);
                pDebugData->pData =
                    Utils::VoidPtrInc(pSlotDebugData, HAL3MetadataUtil::DebugDataOffset(DebugDataType::BPSTuning));
            }
            else
            {
                pDebugData->size = 0;
                pDebugData->pData = NULL;
            }
        }
    }
    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::LockMetadataSubscribers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::LockMetadataSubscribers(
    SIZE_T index)
{
    LightweightDoublyLinkedList* pList = m_pMetadataClients[index];
    if (NULL != pList)
    {
        LDLLNode* pNode = pList->Head();

        while (NULL != pNode)
        {
            SubscriptionEntry* pEntry = static_cast<SubscriptionEntry*>(pNode->pData);

            pEntry->pClient->LockForPublish();

            pNode = LightweightDoublyLinkedList::NextNode(pNode);
        }
    }

    for (UINT client = 0; client < MaxSubscribers; client++)
    {
        SubscriptionEntry* pEntry = &m_subscribeAllClients[client];

        if (NULL != pEntry->pClient)
        {
            pEntry->pClient->LockForPublish();
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::UnlockMetadataSubscribers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::UnlockMetadataSubscribers(
    SIZE_T index)
{
    LightweightDoublyLinkedList* pList = m_pMetadataClients[index];
    if (NULL != pList)
    {
        LDLLNode* pNode = pList->Head();

        while (NULL != pNode)
        {
            SubscriptionEntry* pEntry = static_cast<SubscriptionEntry*>(pNode->pData);

            pEntry->pClient->UnlockAfterPublish();

            pNode = LightweightDoublyLinkedList::NextNode(pNode);
        }
    }

    for (UINT client = MaxSubscribers; client > 0; client--)
    {
        SubscriptionEntry* pEntry = &m_subscribeAllClients[client - 1];

        if (NULL != pEntry->pClient)
        {
            pEntry->pClient->UnlockAfterPublish();
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::LockPropertySubscribers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::LockPropertySubscribers(
    SIZE_T index)
{
    LightweightDoublyLinkedList* pList = m_pPropertyClients[index];

    if (NULL != pList)
    {
        LDLLNode* pNode = pList->Head();

        while (NULL != pNode)
        {
            SubscriptionEntry* pEntry = static_cast<SubscriptionEntry*>(pNode->pData);

            pEntry->pClient->LockForPublish();

            pNode = LightweightDoublyLinkedList::NextNode(pNode);
        }
    }

    for (UINT client = 0; client < MaxSubscribers; client++)
    {
        SubscriptionEntry* pEntry = &m_subscribeAllClients[client];

        if (NULL != pEntry->pClient)
        {
            pEntry->pClient->LockForPublish();
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::UnlockPropertySubscribers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::UnlockPropertySubscribers(
    SIZE_T index)
{
    LightweightDoublyLinkedList* pList = m_pPropertyClients[index];

    if (NULL != pList)
    {
        LDLLNode* pNode = pList->Head();

        while (NULL != pNode)
        {
            SubscriptionEntry* pEntry = static_cast<SubscriptionEntry*>(pNode->pData);

            pEntry->pClient->UnlockAfterPublish();

            pNode = LightweightDoublyLinkedList::NextNode(pNode);
        }
    }

    for (UINT client = MaxSubscribers; client > 0; client--)
    {
        SubscriptionEntry* pEntry = &m_subscribeAllClients[client - 1];

        if (NULL != pEntry->pClient)
        {
            pEntry->pClient->UnlockAfterPublish();
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Lock
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::Lock() const
{
    m_pPoolLock->Lock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataPool::Unlock
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataPool::Unlock() const
{
    m_pPoolLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot Methods
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::Create
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MetadataSlot* MetadataSlot::Create(
    SIZE_T        entryCapacity,
    SIZE_T        dataSize,
    MetadataPool* pPool)
{
    CamxResult      result          = CamxResultEFailed;
    MetadataSlot*   pLocalInstance  = NULL;

    pLocalInstance = CAMX_NEW MetadataSlot(entryCapacity, dataSize, pPool);
    if (NULL != pLocalInstance)
    {
        result = pLocalInstance->Initialize();
        if (CamxResultSuccess != result)
        {
            CAMX_DELETE pLocalInstance;
            pLocalInstance = NULL;
        }
    }

    return pLocalInstance;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::Destroy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::Destroy()
{
    CAMX_DELETE this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::MetadataSlot
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MetadataSlot::MetadataSlot(
    SIZE_T        entryCapacity,
    SIZE_T        dataSize,
    MetadataPool* pPool)
    : m_dataSize(dataSize)
    , m_entryCapacity(entryCapacity)
    , m_pPool(pPool)
{
    m_tagCount       = HAL3MetadataUtil::GetAllMetadataTagCount();
    CAMX_ASSERT(0 != m_tagCount);
    m_ppStickyPtr    = reinterpret_cast<VOID**> (CAMX_CALLOC(sizeof(VOID*)  * m_tagCount));
    CAMX_ASSERT(NULL != m_ppStickyPtr);
    m_pStickyCount   = reinterpret_cast<SIZE_T*>(CAMX_CALLOC(sizeof(SIZE_T) * m_tagCount));
    CAMX_ASSERT(NULL != m_pStickyCount);
    m_pRequestTag    = reinterpret_cast<UINT32*>(CAMX_CALLOC(sizeof(UINT32) * m_tagCount));
    CAMX_ASSERT(NULL != m_pRequestTag);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::~MetadataSlot
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MetadataSlot::~MetadataSlot()
{
    if (NULL != m_pMetadata)
    {
        PoolType poolType = m_pPool->GetPoolType();

        switch (poolType)
        {
            case PoolType::Static:
            case PoolType::PerFrameResult:
            case PoolType::PerFrameResultEarly:
            case PoolType::PerFrameEmptyMetadata:
                HAL3MetadataUtil::FreeMetadata(m_pMetadata);
                break;

            case PoolType::PerUsecase:
                HAL3MetadataUtil::FreeMetadata(m_pMetadata);
                CAMX_FREE(m_pMetadataBlob);
                m_pMetadataBlob = NULL;
                break;

            case PoolType::PerFrameInternal:
            case PoolType::PerFrameDebugData:
                CAMX_FREE(m_pMetadata);
                m_pMetadata = NULL;
                break;

            default:
                break;
        }

        m_pMetadata = NULL;
    }

    if (NULL != m_ppStickyPtr)
    {
        CAMX_FREE(m_ppStickyPtr);
        m_ppStickyPtr = NULL;
    }

    if (NULL != m_pStickyCount)
    {
        CAMX_FREE(m_pStickyCount);
        m_pStickyCount = NULL;
    }

    if (NULL != m_pRequestTag)
    {
        CAMX_FREE(m_pRequestTag);
        m_pRequestTag = NULL;
    }

    if (NULL != m_pRWLock)
    {
        m_pRWLock->Destroy();
        m_pRWLock = NULL;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::Initialize
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataSlot::Initialize()
{
    CamxResult result   = CamxResultSuccess;
    PoolType   poolType = m_pPool->GetPoolType();
    VOID*      pBlob;

    switch (poolType)
    {
        case PoolType::Static:
        case PoolType::PerFrameResult:
        case PoolType::PerFrameResultEarly:
            m_pMetadata = HAL3MetadataUtil::CreateMetadata(m_entryCapacity, m_dataSize);
            GetPropertyBlob(&pBlob);
            break;

        case PoolType::PerFrameEmptyMetadata:
            m_pMetadata = HAL3MetadataUtil::CreateMetadata(m_entryCapacity, m_dataSize);
            break;

        case PoolType::PerUsecase:
            m_pMetadata     = HAL3MetadataUtil::CreateMetadata(m_entryCapacity, m_dataSize);
            m_pMetadataBlob = CAMX_CALLOC(sizeof(UsecasePropertyBlob));
            break;

        case PoolType::PerFrameInternal:
        case PoolType::PerFrameDebugData:
            m_pMetadata = CAMX_CALLOC(m_dataSize);
            break;
        case PoolType::PerFrameInput:
            // No metadata handled by Input type, only pointers
        default:
            break;
    }

    if ((NULL == m_pMetadata) && !(PoolType::PerFrameInput == poolType || PoolType::PerFrameEmptyMetadata == poolType))
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Couldn't allocate memory for metadata slot because of invalid pool type: ",
                       static_cast<UINT>(poolType));
        result = CamxResultEFailed;
    }

    if ((NULL == m_pMetadataBlob) && (PoolType::PerUsecase == poolType))
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Couldn't allocate memory for metadata slot for pool type: ",
                       static_cast<UINT>(poolType));
        result = CamxResultENoMemory;
    }

    if (CamxResultSuccess == result)
    {
        m_pRWLock = ReadWriteLock::Create(m_pPool->GetPoolIdentifier());
        if (NULL == m_pRWLock)
        {
            result = CamxResultEFailed;
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::GetMetadata
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Metadata* MetadataSlot::GetMetadata()
{
    return m_pMetadata;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::GetMetadataByIndex
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::GetMetadataByIndex(
    UINT32  index,
    VOID**  ppData)
{
    if (FALSE == m_isValid)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid Slot to get a metadata from");
        *ppData = NULL;
        return;
    }

    if (NULL == ppData)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "NULL result data pointer");
        return;
    }

    PoolType poolType = m_pPool->GetPoolType();

    // Index operations only supported on Input
    CAMX_ASSERT((PoolType::PerFrameInput == poolType));

    if (PoolType::PerFrameInput == poolType)
    {
        *ppData = m_ppStickyPtr[index];
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::GetMetadataByTag
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::GetMetadataByTag(
    UINT32  tag,
    VOID**  ppData)
{
    CAMX_ENTRYEXIT(CamxLogGroupStats);

    if (FALSE == m_isValid)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid Slot to get a metadata from");
        *ppData = NULL;
        return;
    }

    if (NULL == ppData)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "NULL result data pointer");
        return;
    }

    PoolType poolType = m_pPool->GetPoolType();

    CAMX_ASSERT((PoolType::PerFrameInput == poolType) || (NULL != m_pMetadata));
    CAMX_ASSERT(PoolType::PerFrameInternal != poolType);

    if ((NULL                       != m_pMetadata) &&
        (PoolType::PerFrameInternal != poolType))
    {
        // It is expected the caller has already taken the lock by calling ReadLock() before calling this function
        HAL3MetadataUtil::GetMetadata(m_pMetadata, tag, ppData);
        // It is expected that the caller will release the lock by calling Unlock() after this function returns
    }
    else if (PoolType::PerFrameInput == poolType)
    {
        *ppData = m_ppStickyPtr[CAMX_TAG_TO_INDEX(tag)];
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::GetMetadataCountByTag
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SIZE_T MetadataSlot::GetMetadataCountByTag(
    UINT32  tag,
    BOOL    allowSticky)
{
    CAMX_ENTRYEXIT(CamxLogGroupStats);

    if (FALSE == m_isValid)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid Slot to get a metadata from");
        return 0;
    }

    PoolType poolType = m_pPool->GetPoolType();
    SIZE_T   count    = 0;

    CAMX_ASSERT((PoolType::PerFrameInput == poolType) || (NULL != m_pMetadata));
    CAMX_ASSERT(PoolType::PerFrameInternal != poolType);

    if ((NULL                       != m_pMetadata) &&
        (PoolType::PerFrameInternal != poolType))
    {
        // It is expected the caller has already taken the lock by calling ReadLock() before calling this function
        count = HAL3MetadataUtil::GetMetadataCount(m_pMetadata, tag);
        // It is expected that the caller will release the lock by calling Unlock() after this function returns
    }
    else if (PoolType::PerFrameInput == poolType)
    {
        UINT32 index = CAMX_TAG_TO_INDEX(tag);

        // If we can use the sticky, the value is always latest
        // If we cant use sticky check whether it was provided in the request as indicated by isMetadataTagPublished
        if ((TRUE == allowSticky) || (TRUE == m_isMetadataTagPublished[index]))
        {
            count = m_pStickyCount[index];
        }
    }

    return count;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::CopyPublishedMetadataFromTag
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::CopyPublishedMetadataFromTag(
    UINT32        tag,
    VOID*         pData,
    SIZE_T        size)
{
    VOID* pValue = NULL;

    if (TRUE == IsPublished(UnitType::Metadata, tag))
    {
        GetMetadataByTag(tag, &pValue);

        if (NULL != pValue)
        {
            CamX::Utils::Memcpy(pData, pValue, size);
        }
        else
        {
            CAMX_LOG_ERROR(CamxLogGroupHAL, "pValue is NULL");
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::GetMetadataListByTags
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::GetMetadataListByTags(
    UINT    numTags,
    UINT32* pTagList,
    VOID**  ppData)
{
    if (FALSE == m_isValid)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid Slot to get metadata from");
        return;
    }

    PoolType poolType = m_pPool->GetPoolType();

    CAMX_ASSERT(numTags < MaxMetadataTags);

    if (numTags < MaxMetadataTags)
    {
        if ((PoolType::PerFrameInput == poolType) || (PoolType::PerFrameDebugData == poolType))
        {
            for (UINT tagIndex = 0; tagIndex < numTags; tagIndex++)
            {
                CAMX_ASSERT(NULL == ppData[tagIndex]);
                ppData[tagIndex] = m_ppStickyPtr[CAMX_TAG_TO_INDEX(pTagList[tagIndex])];
            }
        }
        else if ((NULL != m_pMetadata)                     &&
                 (PoolType::PerFrameInternal != poolType))
        {
            // It is expected the caller has already taken the lock by calling ReadLock() before calling this function
            for (UINT tagIndex = 0; tagIndex < numTags; tagIndex++)
            {
                CAMX_ASSERT(NULL == ppData[tagIndex]);
                HAL3MetadataUtil::GetMetadata(m_pMetadata, pTagList[tagIndex], &ppData[tagIndex]);
            }
            // It is expected that the caller will release the lock by calling Unlock() after this function returns
        }
    }

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::SetMetadataByTag
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataSlot::SetMetadataByTag(
    UINT32      tag,
    const VOID* pData,
    SIZE_T      count)
{
    CamxResult    result   = CamxResultEFailed;
    PoolType      poolType = m_pPool->GetPoolType();

    if (FALSE == m_isValid)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid slot; cannot set metadata tag %08x", tag);
        return result;
    }

    CAMX_ASSERT(NULL != pData);
    CAMX_ASSERT(PoolType::PerFrameInternal != poolType);
    CAMX_ASSERT(PoolType::PerFrameInput != poolType);
    CAMX_ASSERT(PoolType::PerFrameDebugData != poolType);

    // We take and release the lock explicitly for write operations
    if ((NULL                           != m_pMetadata) &&
        (NULL                           != pData)       &&
        (PoolType::PerFrameInternal     != poolType)    &&
        (PoolType::PerFrameInput        != poolType)    &&
        (PoolType::PerFrameDebugData    != poolType))
    {
        // Disallow updating a result pool tag that can have variable entry count to ensure no updates shift data
        BOOL updateAllowed =
            ((PoolType::PerFrameResult == poolType) && (TagFactor[CAMX_TAG_TO_INDEX(tag)] != 1)) ? FALSE : TRUE;

        m_pRWLock->WriteLock();
        result = HAL3MetadataUtil::UpdateMetadata(m_pMetadata, tag, pData, count, updateAllowed);
        m_pRWLock->Unlock();
    }
    CAMX_ASSERT_MESSAGE((CamxResultSuccess == result), "Setting metadata failed for tag: %08x, with count: %d", tag, count);

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::GetPropertyBlob
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::GetPropertyBlob(
    VOID** ppBlob)
{
    if (NULL == ppBlob)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid get pointer");
        return;
    }

    // It is expected the caller has already taken the lock by calling ReadLock() before calling this function

    PoolType          poolType = m_pPool->GetPoolType();
    MainPropertyBlob* pBlob    = NULL;

    switch (poolType)
    {
        case PoolType::PerFrameResult:
        case PoolType::PerFrameResultEarly:
            if (NULL != m_pMetadata)
            {
                HAL3MetadataUtil::GetPropertyBlob(m_pMetadata, &pBlob);
                *ppBlob = reinterpret_cast<VOID*>(pBlob);
            }
            break;

        case PoolType::PerUsecase:
            if (NULL != m_pMetadataBlob)
            {
                *ppBlob = reinterpret_cast<VOID*>(m_pMetadataBlob);
            }
            break;

        case PoolType::PerFrameInternal:
        case PoolType::PerFrameDebugData:
            *ppBlob = m_pMetadata;
            break;
        case PoolType::PerFrameInput:
            *ppBlob = m_ppStickyPtr[m_pPool->GetInputPoolPropertyOffset()];
            break;
        default:
            CAMX_ASSERT_ALWAYS_MESSAGE("Unable to get Property blob for pool type: %d", static_cast<UINT>(poolType));
            break;
    }

    // It is expected that the caller will release the lock by calling Unlock() after this function returns
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::PublishMetadataTag
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataSlot::PublishMetadataTag(
    UINT32 tag)
{
    CAMX_ASSERT(PoolType::PerFrameInput != m_pPool->GetPoolType());

    SIZE_T index       = CAMX_TAG_TO_INDEX(tag);

    if ((InvalidIndex == index) ||
        (index >= MaxMetadataTags))
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid tag %d to publish", tag);
        return  CamxResultENoSuch;
    }

    m_pPool->LockMetadataSubscribers(index);
    {
        m_isMetadataTagPublished[index] = TRUE;
        m_pPool->NotifyImmediate(UnitType::Metadata, tag, index, m_slotRequestId, TRUE);
    }
    m_pPool->UnlockMetadataSubscribers(index);

    return CamxResultSuccess;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::PublishProperty
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataSlot::PublishProperty(
    PropertyID property)
{
    PoolType      poolType    = m_pPool->GetPoolType();
    UINT32        index       = m_pPool->FindIndexByPropertyId(property);

    if ((InvalidIndex == index)                 ||
        (PoolType::Static == poolType)          ||
        (PoolType::PerFrameInput == poolType)   ||
        (index >= MaxProperties))
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Cannot publish property:%d", property);
        return CamxResultENoSuch;
    }

    if (0 == m_slotRequestId)
    {
        m_isValid = TRUE; // Make the slot valid

        CAMX_LOG_INFO(CamxLogGroupCore, "We are publishing to a future slot which is not taken yet");
    }

    m_pPool->LockPropertySubscribers(index);
    {
        m_isPropertyPublished[index] = TRUE;
        m_pPool->NotifyImmediate(UnitType::Property, property, index, m_slotRequestId, TRUE);
    }
    m_pPool->UnlockPropertySubscribers(index);

    return CamxResultSuccess;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::PublishMetadataList
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataSlot::PublishMetadataList(
    UINT32* pTagList,
    UINT32  numTags)
{
    CamxResult resultFinal = CamxResultSuccess;

    CAMX_ASSERT(PoolType::PerFrameInput     != m_pPool->GetPoolType());
    CAMX_ASSERT(PoolType::PerFrameInternal  != m_pPool->GetPoolType());
    CAMX_ASSERT(NULL != pTagList);

    if (FALSE == m_isValid)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "Invalid slot, cannot publish metadata list");
        resultFinal = CamxResultEInvalidState;
    }
    else
    {
        if (NULL != pTagList)
        {
            for (UINT32 i = 0; i < numTags; i++)
            {
                CamxResult resultLocal = PublishMetadataTag(pTagList[i]);

                // We don't abort if a single publish failed
                if (CamxResultSuccess != resultLocal)
                {
                    resultFinal = resultLocal;
                }
            }
        }
    }

    return resultFinal;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::Invalidate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::Invalidate()
{
    m_pRWLock->WriteLock();

    m_slotRequestId  = 0;
    m_isValid        = FALSE;
    m_lastRequestTag = 0;

    Utils::Memset(&m_isPropertyPublished[0], 0, (sizeof(BOOL) * MaxProperties));
    Utils::Memset(&m_isMetadataTagPublished[0], 0, (sizeof(BOOL) * MaxMetadataTags));

    m_pRWLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::MergeMetadata
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::MergeMetadata(
    const Metadata* pSrcMetadata)
{
    if (m_pMetadata == NULL)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "m_pMetadata == NULL");
    }
    else if (pSrcMetadata == NULL)
    {
        CAMX_LOG_ERROR(CamxLogGroupHAL, "pSrcMetadata == NULL");
    }
    else
    {
        HAL3MetadataUtil::MergeMetadata(m_pMetadata, pSrcMetadata);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::CopyDisjointTags
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CamxResult MetadataSlot::CopyDisjointTags(
    Metadata* pDstMetadata)
{
    CamxResult result = CamxResultSuccess;
    // Only input supported
    if (PoolType::PerFrameInput == m_pPool->GetPoolType())
    {
        for (UINT i = 0; i < m_lastRequestTag; i++)
        {
            UINT32 tag   = m_pRequestTag[i];
            UINT32 index = CAMX_TAG_TO_INDEX(tag);

            m_pRWLock->WriteLock();
            // Only copy if not in dst metadata
            if (0 == HAL3MetadataUtil::GetMetadataCount(pDstMetadata, tag))
            {
                result = HAL3MetadataUtil::SetMetadata(pDstMetadata, tag, m_ppStickyPtr[index], m_pStickyCount[index]);
            }
            m_pRWLock->Unlock();

            if (CamxResultSuccess != result)
            {
                break;
            }
        }
    }
    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::ResetMetadata
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::ResetMetadata()
{
    PoolType poolType = m_pPool->GetPoolType();

    if ((PoolType::PerFrameResult == poolType) || (PoolType::PerFrameResultEarly == poolType) ||
        (PoolType::PerFrameEmptyMetadata == poolType))
    {
        VOID* pBlob;

        HAL3MetadataUtil::ResetMetadata(m_pMetadata, m_dataSize, m_entryCapacity);
        GetPropertyBlob(&pBlob);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::IsPublished
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL MetadataSlot::IsPublished(
    UnitType    type,
    UINT32      unitId)
{
    BOOL published = FALSE;

    if (FALSE == m_isValid)
    {
        CAMX_LOG_VERBOSE(
            CamxLogGroupHAL,
            "Slot not yet initialized, slotPtr = %08x, requestID = %llu, pool = %d, type = %d, id = %08x",
            this, m_pPool->GetPoolType(), m_slotRequestId, type, unitId);
        published = FALSE;
    }
    else if (PoolType::PerFrameInput == m_pPool->GetPoolType())
    {
        published = TRUE;
    }
    else
    {
        published = ReadPublished(type, unitId);
    }

    return published;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::ReadPublished
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL MetadataSlot::ReadPublished(
    UnitType    type,
    UINT32      unitId)
{
    CAMX_ASSERT(PoolType::PerFrameInput != m_pPool->GetPoolType());

    BOOL   published = FALSE;
    SIZE_T index     = InvalidIndex;

    switch (type)
    {
        case UnitType::Property:
            index = m_pPool->FindIndexByPropertyId(unitId);
            if (InvalidIndex != index)
            {
                published = m_isPropertyPublished[index];
            }
            else
            {
                CAMX_LOG_WARN(CamxLogGroupHAL, "Invalid index, slotPtr = %08x, pool = %d, type = %d, id = %08x",
                    this, m_pPool->GetPoolType(), type, unitId);
            }
            break;

        case UnitType::Metadata:
            index = CAMX_TAG_TO_INDEX(unitId);
            if (InvalidIndex != index)
            {
                published = m_isMetadataTagPublished[index];
            }
            break;

        default:
            break;
    }

    return published;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::SetSlotRequestId
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::SetSlotRequestId(
    UINT64 requestId)
{
    m_slotRequestId = requestId;

    m_isValid = TRUE;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::GetSlotRequestId
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UINT64 MetadataSlot::GetSlotRequestId() const
{
    UINT64 requestId = 0;

    CAMX_ASSERT_MESSAGE((TRUE == m_isValid), "Invalid Slot");

    requestId = m_slotRequestId;

    return requestId;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::ReadLock
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::ReadLock() const
{
    m_pRWLock->ReadLock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::WriteLock
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::WriteLock() const
{
    m_pRWLock->WriteLock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MetadataSlot::Unlock
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::Unlock() const
{
    m_pRWLock->Unlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// MetadataSlot::PrintTagData
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VOID MetadataSlot::PrintTagData(
    UINT32      tag)
{
    HAL3MetadataUtil::PrintTagData(m_pMetadata, tag);
}


CAMX_NAMESPACE_END
